/*************************************************************************
 Crytek Source File.
 Copyright (C), Crytek Studios, 2001-2004.
 -------------------------------------------------------------------------
 $Id$
 $DateTime$
 Description:  implementation of INetwork
 -------------------------------------------------------------------------
 History:
 - 26/07/2004   : Created by Craig Tiller
 !F RCON system - lluo
*************************************************************************/

#ifndef __NETWORK_HEADER__
#define __NETWORK_HEADER__

#pragma once

#include <vector>
#include <memory>
#include "Config.h"
#include "TimeValue.h"
#include "Protocol/StatsCollector.h"
#include "Errors.h"
#include "Socket/NetAddress.h"
#include "INetwork.h"
#include "NetHelpers.h"
#include "WorkQueue.h"
#include "NetTimer.h"
#include "MementoMemoryManager.h"
#include "NetCVars.h"
#include "NetLog.h"
#include "CryThread.h"
#include "STLGlobalAllocator.h"
#include "Protocol/MessageQueue.h"
#include "Socket/IStreamSocket.h"
#include "Socket/IDatagramSocket.h"
#include "Socket/ISocketIOManager.h"

#include "Cryptography/Whirlpool.h"
#include "Protocol/ExponentialKeyExchange.h"
#include "Compression/CompressionManager.h"

#include "Socket/WatchdogTimer.h"

#if defined(WIN32) || defined(WIN64)
#define _WIN32_DCOM
#include <comdef.h>
#include <Wbemidl.h>
# pragma comment(lib, "wbemuuid.lib")
#endif

struct ICVar;
class CNetNub;
class CNetContext;
struct INetworkMember;
struct IConsoleCmdArgs;
class CNetworkInspector;
class CServiceManager;
#if ENABLE_DISTRIBUTED_LOGGER
class CDistributedLogger;
#endif


struct PerformanceGuard
{
	SNetworkPerformance* m_pCounter;
	uint64 m_nOldVal;

	PerformanceGuard(SNetworkPerformance* p, uint64 oldVal)
	{
		m_pCounter = p;
		m_nOldVal = oldVal;
		p->m_nNetworkSync = CryGetTicks();
	}

	~PerformanceGuard()
	{
		m_pCounter->m_nNetworkSync = CryGetTicks() - m_pCounter->m_nNetworkSync + m_nOldVal;
	}

};

struct SSystemBranchCounters
{
	SSystemBranchCounters()
	{
		memset(this, 0, sizeof(*this));
	}
	uint32 updateTickMain;
	uint32 updateTickSkip;
	uint32 iocpReapImmediate;
	uint32 iocpReapImmediateCollect;
	uint32 iocpReapSleep;
	uint32 iocpReapSleepCollect;
	uint32 iocpIdle;
	uint32 iocpForceSync;
	uint32 iocpBackoffCheck;
};

extern SSystemBranchCounters g_systemBranchCounters;

enum EMementoMemoryType
{
	eMMT_Memento = 0,
	eMMT_PacketData,
	eMMT_ObjectData,
	eMMT_Alphabet,
	eMMT_NUM_TYPES
};

template <class T>
class NetMutexLock
{
public:
	NetMutexLock( T& mtx, bool lock ) : m_mtx(mtx), m_lock(lock) { if (m_lock) m_mtx.Lock(); }
	~NetMutexLock() { if (m_lock) m_mtx.Unlock(); }

private:
	T& m_mtx;
	bool m_lock;
};

typedef CryLockT<CRYLOCK_RECURSIVE> NetFastMutex;

enum EDebugMemoryMode
{
	eDMM_Context = 1,
	eDMM_Endpoint = 2,
	eDMM_Mementos = 4,
};

enum EDebugBandwidthMode
{
	eDBM_None = 0,
	eDBM_AspectAgeList = 1,
	eDBM_AspectAgeMap = 2,
	eDBM_PriorityMap = 3,
};

const char * GetTimestampString();

extern CTimeValue g_time;

#define LOOPBACK_ADDRESS (0x7f000001)
#define BROADCAST_ADDRESS (0xffffffffu)

#if NETWORK_REBROADCASTER
// Used by the rebroadcaster to redirect packets
// A destinationID of 0 means the packet has reached it's intended destination
struct SRebroadcasterFrameHeader
{
	uint8 frameHeaderID;
	uint8 hopCount;
	uint16 padding;
	TNetChannelID sourceID;
	TNetChannelID destinationID;
};

#define MAXIMUM_NUMBER_OF_CONNECTIONS 32
#define REBROADCASTER_CHANNEL_NAME_SIZE (16)
struct SRebroadcasterConnection
{
	SRebroadcasterConnection() { Reset(); }

	void Reset(void)
	{
		memset(name, 0, REBROADCASTER_CHANNEL_NAME_SIZE);
		channelID = 0;
	}

	char name[REBROADCASTER_CHANNEL_NAME_SIZE];
	TNetChannelID	channelID;
};

struct SRebroadcasterEntry
{
	SRebroadcasterEntry(void) { Reset(); }

	void Reset(void)
	{
		pChannel = NULL;
		connection.Reset();
		memset(status, 0xff, sizeof(status));
	}

	INetChannel* pChannel;
	SRebroadcasterConnection connection;
	uint8 status[MAXIMUM_NUMBER_OF_CONNECTIONS / 8];
};

enum ERebroadcasterConnectionStatus
{
	eRCS_Disabled = 0,
	eRCS_Enabled,
	eRCS_Unknown
};
#endif // NETWORK_REBROADCASTER

class CNetwork : public INetwork
{
public:
	//! constructor
	CNetwork();
protected:
	//! destructor
	virtual ~CNetwork();

public:
	bool Init( int ncpu );

	// interface INetwork -------------------------------------------------------------

	VIRTUAL bool HasNetworkConnectivity();
	VIRTUAL INetNub * CreateNub( const char * address, 
		IGameNub * pGameNub, 
		IGameSecurity * pSecurity,
		IGameQuery * pGameQuery );
	VIRTUAL ILanQueryListener * CreateLanQueryListener( IGameQueryListener * pGameQueryListener );
	VIRTUAL INetContext * CreateNetContext( IGameContext * pGameContext, uint32 flags );
	virtual const char *EnumerateError(NRESULT err);
	virtual void Release();
	VIRTUAL void GetMemoryStatistics(ICrySizer *pSizer);
	VIRTUAL void GetPerformanceStatistics(SNetworkPerformance *pSizer);
	VIRTUAL void SyncWithGame(ENetworkGameSync);
	VIRTUAL const char * GetHostName();
	VIRTUAL INetworkServicePtr GetService( const char * name );
	VIRTUAL void FastShutdown();
  VIRTUAL void SetCDKey(const char* key);
	VIRTUAL bool PbConsoleCommand(const char *, int length);   // EvenBalance - M. Quinn
	VIRTUAL void PbCaptureConsoleLog(const char* output, int length);   // EvenBalance - M. Quinn
	VIRTUAL void PbServerAutoComplete(const char *, int length);   // EvenBalance - M. Quinn
	VIRTUAL void PbClientAutoComplete(const char *, int length);   // EvenBalance - M. Quinn
	VIRTUAL void SetNetGameInfo( SNetGameInfo );
	VIRTUAL SNetGameInfo GetNetGameInfo();
	VIRTUAL	ICryLobby*	GetLobby();

	VIRTUAL bool IsPbClEnabled();
	VIRTUAL bool IsPbSvEnabled();

	VIRTUAL void StartupPunkBuster(bool server);
	VIRTUAL void CleanupPunkBuster();

	VIRTUAL bool IsPbInstalled();

	VIRTUAL IRemoteControlSystem* GetRemoteControlSystemSingleton();

	VIRTUAL ISimpleHttpServer* GetSimpleHttpServerSingleton();

	// nub helpers
	int GetLogLevel();
	ILINE CNetAddressResolver * GetResolver() { return m_pResolver; }
	ILINE CWorkQueue& GetToGameQueue() { return m_toGame; }
	ILINE CWorkQueue& GetToGameLazyQueue() { return m_toGameLazyBuilding; }
	ILINE CWorkQueue& GetFromGameQueue() { return m_fromGame; }
	ILINE CWorkQueue& GetInternalQueue() { return m_intQueue; }
	ILINE CNetTimer& GetTimer() { return m_timer; }
	ILINE const CNetCVars& GetCVars() { return m_cvars; }
	ILINE CMessageQueue::CConfig * GetMessageQueueConfig() const { return m_pMessageQueueConfig; }
	ILINE CCompressionManager& GetCompressionManager() { return m_compressionManager; }
	ILINE ISocketIOManager& GetSocketIOManager() { return *m_pSocketIOManager; }
	int GetMessageQueueConfigVersion() const { return m_schedulerVersion; }

	CTimeValue GetGameTime() { return m_gameTime; }

	void ReportGotPacket() { m_detection.ReportGotPacket(); }

	ILINE CStatsCollector * GetStats()  
	{ 
		static CStatsCollector m_stats("netstats.log");
		return &m_stats; 
	}

#if ENABLE_DEBUG_KIT
	ILINE CNetworkInspector* GetNetInspector()
	{
		return m_pNetInspector;
	}
#endif

	void WakeThread();

	static ILINE CNetwork * Get() { return m_pThis; }
	ILINE NetFastMutex& GetMutex() { return m_mutex; }
	ILINE NetFastMutex& GetCommMutex() { return m_commMutex; }
	ILINE NetFastMutex& GetLogMutex() { return m_logMutex; }

	ILINE bool IsMultithreaded() const { return (m_multithreadedMode!=NETWORK_MT_OFF); }
	void SetMultithreadingMode( ENetwork_Multithreading_Mode threadingMode );

	void ReloadScheduler();

	CNetChannel * FindFirstClientChannel();

  string GetCDKey()const
  {
    return m_CDKey;
  }

  CServiceManager* GetServiceManager(){return m_pServiceManager.get();}

	// fast lookup of channels (mainly for punkbuster)
	int RegisterForFastLookup(CNetChannel * pChannel);
	void UnregisterFastLookup(int id);
	CNetChannel * GetChannelByFastLookupId(int id);

	void AddExecuteString( const string& toExec );

	void BroadcastNetDump( ENetDumpType );

	// small hack to make adding thigns to the from game queue in general efficient
#define ADDTOFROMGAMEQUEUE_BODY(params) if (IsPrimaryThread()) m_fromGame.Add params; else { CryAutoLock<NetFastMutex> lk(m_fromGame_otherThreadLock); m_fromGame_otherThreadQueue.Add params; }
	template <class A> void AddToFromGameQueue( const A& a )
	{ ADDTOFROMGAMEQUEUE_BODY((a)); }
	template <class A, class B> void AddToFromGameQueue( const A& a, const B& b )
	{ ADDTOFROMGAMEQUEUE_BODY((a, b)); }
	template <class A, class B, class C> void AddToFromGameQueue( const A& a, const B& b, const C& c )
	{ ADDTOFROMGAMEQUEUE_BODY((a, b, c)); }
	template <class A, class B, class C, class D> void AddToFromGameQueue( const A& a, const B& b, const C& c, const D& d )
	{ ADDTOFROMGAMEQUEUE_BODY((a, b, c, d)); }
	template <class A, class B, class C, class D, class E> void AddToFromGameQueue( const A& a, const B& b, const C& c, const D& d, const E& e )
	{ ADDTOFROMGAMEQUEUE_BODY((a, b, c, d, e)); }
	template <class A, class B, class C, class D, class E, class F> void AddToFromGameQueue( const A& a, const B& b, const C& c, const D& d, const E& e, const F& f )
	{ ADDTOFROMGAMEQUEUE_BODY((a, b, c, d, e, f)); }
	template <class A, class B, class C, class D, class E, class F, class G> void AddToFromGameQueue( const A& a, const B& b, const C& c, const D& d, const E& e, const F& f, const G& g)
	{ ADDTOFROMGAMEQUEUE_BODY((a, b, c, d, e, f, g)); }

	VIRTUAL bool NpGetChildFromCurrent(const char *name, SNetProfileStackEntry **entry, bool rmi);
	VIRTUAL void NpRegisterBeginCall(const char *name, SNetProfileStackEntry** entry, float budget, bool rmi);
	VIRTUAL void NpBeginFunction(SNetProfileStackEntry* entry, bool read);
	VIRTUAL void NpEndFunction();
	VIRTUAL bool NpIsInitialised();
	VIRTUAL SNetProfileStackEntry* NpGetNullProfile();

	/////////////////////////////////////////////////////////////////////////////
	// Rebroadcaster

	// IsRebroadcasterEnabled
	// Informs the caller if the rebroadcaster is enabled or not
	VIRTUAL bool IsRebroadcasterEnabled(void) const;

	// AddRebroadcasterConnection
	// Adds a connection to the rebroadcaster mesh
	// pChannel		- pointer to the channel being added
	// channelID	- game side channel ID associated with pChannel (if known)
	VIRTUAL void AddRebroadcasterConnection(INetChannel* pChannel, TNetChannelID channelID);
	/////////////////////////////////////////////////////////////////////////////

	/////////////////////////////////////////////////////////////////////////////
	// Host Migration
public:
	VIRTUAL void EnableHostMigration(bool bEnabled);
	VIRTUAL bool IsHostMigrationEnabled(void);

	VIRTUAL void TerminateHostMigration(CrySessionHandle gh);
	VIRTUAL void AddHostMigrationEventListener(IHostMigrationEventListener* pListener, const char* pWho);
	VIRTUAL void RemoveHostMigrationEventListener(IHostMigrationEventListener* pListener);

#if NETWORK_HOST_MIGRATION
	THostMigrationEventListenerVector& GetHostMigrationListeners(void) { return m_hostMigrationListeners; }
#endif
	/////////////////////////////////////////////////////////////////////////////

	// Exposed block encryption
	VIRTUAL void EncryptBuffer(uint8* pOutput, const uint8* pInput, uint32 bufferLength, const uint8* pKey, uint32 keyLength);
	VIRTUAL void DecryptBuffer(uint8* pOutput, const uint8* pInput, uint32 bufferLength, const uint8* pKey, uint32 keyLength);

	// Exposed streamed encryption
	VIRTUAL TCipher BeginCipher(const uint8* pKey, uint32 keyLength);
	VIRTUAL void Encrypt(TCipher cipher, uint8* pOutput, const uint8* pInput, uint32 bufferLength);
	VIRTUAL void Decrypt(TCipher cipher, uint8* pOutput, const uint8* pInput, uint32 bufferLength);
	VIRTUAL void EndCipher(TCipher cipher);

	VIRTUAL void GetBandwidthStatistics(SBandwidthStats *pStats);
	VIRTUAL void GetDebugStats(SNetworkDebugStats* const pDebugStats);

	void SetNetworkThreadPriority(ENetwork_Multithreading_Mode threadingMode);

	static bool DecodeAddress(const TNetAddress& address, uint32* pIP, uint16* pPort);
	static void DecodeAddress(uint32 ip, uint16 port, char* ipstring, bool ignorePort);
	static void DecodeAddress(const TNetAddress& address, char* ipstring, bool ignorePort);

	bool IsMinimalUpdate() { return m_allowMinimalUpdate; }

private:
	static CNetwork * m_pThis;
	
	bool m_allowMinimalUpdate;
	bool m_bOverideChannelTickToGoNow;

	SNetworkPerformance m_networkPerformance;

	// NOTE: NetCVars is the most dependent component in CryNetwork, it should be constructed before any other CryNetwork components and
	// destructed after all other CryNetwork components get destroyed (the order of member contructions is the order of declarations of
	// the class, and the order of member destruction is the reverse order of member constructions)
	CNetCVars m_cvars;

	// m_vFastChannelLookup must be constructed before any of the CWorkQueue members
	std::vector<CNetChannel*, stl::STLGlobalAllocator<CNetChannel*> > m_vFastChannelLookup;

	void LogNetworkInfo();
	void DoSyncWithGame(ENetworkGameSync);
	void DoSyncWithGameMinimal();
	void AddMember( INetworkMemberPtr pMember );
	// will all of our members will be dead soon?
	bool AllSuicidal();
	void UpdateLoop( ENetworkGameSync sync );
#if ENABLE_DISTRIBUTED_LOGGER
	std::auto_ptr<CDistributedLogger> m_pLogger;
#endif

	typedef std::map<NRESULT,string> TErrorMap;
	typedef std::vector<INetworkMemberPtr, stl::STLGlobalAllocator<INetworkMemberPtr> > VMembers;

	struct SNetError
	{
		NRESULT nrErrorCode; 
		const char *sErrorDescription;
	};

	TErrorMap               m_mapErrors;
	static SNetError        m_neNetErrors[];
	VMembers                m_vMembers;

	bool                    m_bQuitting;

	CNetAddressResolver * m_pResolver;
#if ENABLE_DEBUG_KIT
	CNetworkInspector *m_pNetInspector;
#endif
	// m_vFastChannelLookup must be constructed before any of the CWorkQueue's
	CWorkQueue       m_toGame;
	CWorkQueue       m_fromGame;
	NetFastMutex     m_fromGame_otherThreadLock;
	CWorkQueue       m_fromGame_otherThreadQueue;
	CWorkQueue       m_intQueue;
	CWorkQueue       m_toGameLazyBuilding;
	CWorkQueue       m_toGameLazyProcessing;
	CNetTimer        m_timer;
	CCompressionManager m_compressionManager;
	CMessageQueue::CConfig * m_pMessageQueueConfig;
	int m_schedulerVersion;
	int m_inSync[eNGS_NUM_ITEMS+2]; // one extra for the continuous update loop, and the other for flushing the lazy queue

	std::auto_ptr<CServiceManager> m_pServiceManager;

	class CNetworkThread;
	std::auto_ptr<CNetworkThread> m_pThread;
	NetFastMutex m_mutex;
	NetFastMutex m_commMutex;
	NetFastMutex m_logMutex;

	/////////////////////////////////////////////////////////////////////////////
	// Host Migration event listener
#if NETWORK_HOST_MIGRATION
	THostMigrationEventListenerVector m_hostMigrationListeners;
#endif
	//
	/////////////////////////////////////////////////////////////////////////////

  CTimeValue m_gameTime;
  
  string m_CDKey;

	class CBufferWakeups;
	int m_bufferWakeups;
	bool m_wokenUp;

	ENetwork_Multithreading_Mode m_multithreadedMode;
	int m_occasionalCounter;
	int m_cleanupMember;
	CTimeValue m_nextCleanup;
	std::vector<string> m_consoleCommandsToExec;
	ISocketIOManager * m_pSocketIOManager;
	int m_cpuCount;

	CTimeValue m_lastUpdateLock;
	bool m_forceLock;

	SNetGameInfo m_gameInfo;

	void RunTests();
	bool UpdateTick( bool mt );

	bool m_isPbSvActive;
	bool m_isPbClActive;

	class CNetworkConnectivityDetection
	{
	public:
		CNetworkConnectivityDetection() : m_hasNetworkConnectivity(true), m_lastCheck(0.0f), m_lastPacketReceived(0.0f) {}

		bool HasNetworkConnectivity();
		void ReportGotPacket() { m_lastPacketReceived = MAX( g_time, m_lastPacketReceived ); }

		void AddRef() {}
		void Release() {}
		bool IsDead() { return false; }

	private:
		bool m_hasNetworkConnectivity;
		CTimeValue m_lastCheck;
		CTimeValue m_lastPacketReceived;

		void DetectNetworkConnectivity();
	};

	CNetworkConnectivityDetection m_detection; // NOTE: this relies on NetTimer and WorkQueue objects to function correctly, so it must be declared/defined after them
};


#if !defined(_DEBUG) || defined(PS3) || defined(XENON)
# define ASSERT_MUTEX_LOCKED(mtx)
# define ASSERT_MUTEX_UNLOCKED(mtx)
#else
# define ASSERT_MUTEX_LOCKED(mtx) if (CNetwork::Get()->IsMultithreaded()) NET_ASSERT((mtx).IsLocked())
# define ASSERT_MUTEX_UNLOCKED(mtx) if (CNetwork::Get()->IsMultithreaded()) NET_ASSERT(!(mtx).IsLocked())
#endif

#define ASSERT_GLOBAL_LOCK ASSERT_MUTEX_LOCKED(CNetwork::Get()->GetMutex())
#define ASSERT_COMM_LOCK ASSERT_MUTEX_LOCKED(CNetwork::Get()->GetCommMutex())

extern int g_lockCount;
#define SCOPED_GLOBAL_LOCK_NO_LOG \
	ASSERT_MUTEX_UNLOCKED(CNetwork::Get()->GetCommMutex()); \
	NetMutexLock<NetFastMutex> globalLock(CNetwork::Get()->GetMutex(), CNetwork::Get()->IsMultithreaded()); \
	g_lockCount++; \
	CAutoUpdateWatchdogCounters updateWatchdogTimers
#if LOG_LOCKING
# define SCOPED_GLOBAL_LOCK SCOPED_GLOBAL_LOCK_NO_LOG; NetLog("lock %s", __FUNCTION__) ; ENSURE_REALTIME
#else
# define SCOPED_GLOBAL_LOCK SCOPED_GLOBAL_LOCK_NO_LOG ; ENSURE_REALTIME
#endif

// should be used in the NetLog functions only
#define SCOPED_GLOBAL_LOG_LOCK CryAutoLock<NetFastMutex> globalLogLock(CNetwork::Get()->GetLogMutex())
#define SCOPED_COMM_LOCK NetMutexLock<NetFastMutex> commMutexLock(CNetwork::Get()->GetCommMutex(), CNetwork::Get()->IsMultithreaded())

// never place these macros in a block without braces...
// BAD: if(x) TO_GAME(blah);
// GOOD: if(x) {TO_GAME(blah);}

// pass a message callback from network system to game, to execute during SyncToGame
// must hold the global lock
// global lock will be held in the callback
#define TO_GAME ASSERT_GLOBAL_LOCK; CNetwork::Get()->GetToGameQueue().Add
// pass a message callback from network system to game, to execute in parallel with the network thread
// global lock will *NOT* be held in the callback
#define TO_GAME_LAZY ASSERT_GLOBAL_LOCK; CNetwork::Get()->GetToGameLazyQueue().Add
// pass a message from the game to the network; 
// not thread-safe!
// the game must guarantee that its calls to the network system are synchronized
// the network code must guarantee that it never calls FROM_GAME in one of its threads
// global lock will be held in the callback
#define FROM_GAME CNetwork::Get()->AddToFromGameQueue
// pass a message from the network engine to the network engine to be executed later
// must hold the global lock 
// global lock will be held in the callback
#define NET_TO_NET ASSERT_GLOBAL_LOCK; CNetwork::Get()->WakeThread(); CNetwork::Get()->GetInternalQueue().Add

#define RESOLVER (*CNetwork::Get()->GetResolver())
#define TIMER CNetwork::Get()->GetTimer()
#define CVARS CNetwork::Get()->GetCVars()
#define NET_INSPECTOR (*CNetwork::Get()->GetNetInspector())
#define STATS (*CNetwork::Get()->GetStats())

class CMementoMemoryRegion
{
public:
	ILINE CMementoMemoryRegion( CMementoMemoryManager * pMMM )
	{
		ASSERT_GLOBAL_LOCK;
		m_pPrev = m_pMMM;
		m_pMMM = pMMM;
	}

	ILINE ~CMementoMemoryRegion()
	{
		ASSERT_GLOBAL_LOCK;
		m_pMMM = m_pPrev;
	}

	static ILINE CMementoMemoryManager& Get()
	{
		assert(m_pMMM);
		return *m_pMMM;
	}

private:
	CMementoMemoryRegion( const CMementoMemoryRegion& );
	CMementoMemoryRegion& operator=( const CMementoMemoryRegion& );

	CMementoMemoryManager * m_pPrev;
	static CMementoMemoryManager * m_pMMM;
};

#define MMM_REGION(pMMM) CMementoMemoryRegion _mmmrgn(pMMM)

ILINE CMementoMemoryManager& MMM()
{
	return CMementoMemoryRegion::Get();
}

#endif
